# -*- coding: utf-8 -*-
# Scikit-learn-3.py
"""
#### 题目3：K均值聚类进行顾客分群（聚类问题）
**任务描述**：  
使用顾客购物中心数据集（Mall Customer Dataset）进行顾客分群。该数据集包含顾客的年龄、年收入和消费得分。  
**步骤**：  
1. 加载数据集（如果没有，可以从网上下载，例如从Kaggle：https://www.kaggle.com/vjchoudhary7/customer-segmentation-tutorial-in-python 下载Mall_Customers.csv）。  
2. 选择特征：'Age', 'Annual Income (k$)', 'Spending Score (1-100)'。  
3. 数据标准化（使用StandardScaler）。  
4. 使用K均值聚类，尝试不同的K值（2到10），通过肘部法则（Elbow Method）确定最佳K值（使用SSE，即簇内平方和）。  
5. 使用最佳K值训练K均值模型。  
6. 可视化聚类结果：绘制3D散点图（三个特征分别代表三个坐标轴），不同颜色代表不同簇。
"""

# 添加环境变量设置以避免OpenMP线程问题
import os
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"

# 导入模块
from sklearn.preprocessing import StandardScaler        # 导入数据标准化函数
from sklearn.cluster import KMeans                      # 导入K均值聚类算法
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import pandas as pd                                     # 新增pandas库
import numpy as np

# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("实验三：K均值聚类进行顾客分群 (K-means Clustering for Customer Segmentation)")
print("=" * 60)

# 1. 加载数据集
print("正在加载数据集... (Loading dataset...)")
try:
    # 尝试从本地加载数据集
    df = pd.read_csv('Scikit-Learning/datasets/Mall_Customers.csv')
    print("成功加载本地数据集 'Mall_Customers.csv' (Successfully loaded local dataset 'Mall_Customers.csv')")
except FileNotFoundError:
    print("本地数据集未找到，请确保 'Mall_Customers.csv' 文件存在于Scikit-Learning目录 (Local dataset not found)")
    print("可以从以下地址下载: https://www.kaggle.com/vjchoudhary7/customer-segmentation-tutorial-in-python")
    exit()

print(f"数据集形状: {df.shape} (Dataset shape)")
print("\n数据集前5行: (First 5 rows of dataset)")
print(df.head())

# 特征名称中英文对照
feature_names_en_cn = {
    'CustomerID': '客户ID (CustomerID)',
    'Genre': '性别 (Genre)',
    'Age': '年龄 (Age)',
    'Annual Income (k$)': '年收入(千美元) (Annual Income (k$))',
    'Spending Score (1-100)': '消费评分(1-100分) (Spending Score (1-100))'
}

# 2. 选择特征
print("\n选择特征: 'Age', 'Annual Income (k$)', 'Spending Score (1-100)'")
print("(Selected features: 'Age', 'Annual Income (k$)', 'Spending Score (1-100)')")
features = df[['Age', 'Annual Income (k$)', 'Spending Score (1-100)']]
print(f"特征数据形状: {features.shape} (Feature data shape)")

# 3. 数据标准化
print("\n正在进行数据标准化... (Performing data standardization...)")
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)
print("标准化完成! (Standardization completed!)")

# 4. 使用肘部法则确定最佳K值
print("\n使用肘部法则确定最佳K值... (Using elbow method to determine optimal K value...)")
sse = []  # 保存每个K值的SSE
silhouette_scores = []  # 保存每个K值的轮廓系数
k_range = range(2, 11)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
    kmeans.fit(features_scaled)
    sse.append(kmeans.inertia_)  # 获取SSE
    silhouette_scores.append(silhouette_score(features_scaled, kmeans.labels_))

# 绘制肘部法则图
plt.figure(figsize=(12, 5))

# SSE图
plt.subplot(1, 2, 1)
plt.plot(k_range, sse, 'bx-')
plt.xlabel('K值 (K Value)')
plt.ylabel('SSE (簇内平方和) (Sum of Squared Errors)')
plt.title('肘部法则 - SSE随K值变化\n(Elbow Method - SSE vs K Value)')
plt.grid(alpha=0.3)

# 轮廓系数图
plt.subplot(1, 2, 2)
plt.plot(k_range, silhouette_scores, 'rx-')
plt.xlabel('K值 (K Value)')
plt.ylabel('轮廓系数 (Silhouette Score)')
plt.title('轮廓系数随K值变化\n(Silhouette Score vs K Value)')
plt.grid(alpha=0.3)

plt.tight_layout()

# 保存肘部法则图片
save_dir = "Scikit-Learning/img"
os.makedirs(save_dir, exist_ok=True)
elbow_path = os.path.join(save_dir, "customer_elbow_method.png")
plt.savefig(elbow_path, dpi=300, bbox_inches='tight')
plt.show()

print(f"肘部法则图已保存为 '{elbow_path}' (Elbow method plot saved as '{elbow_path}')")

# 根据轮廓系数选择最佳K值
best_k = k_range[np.argmax(silhouette_scores)]
print(f"根据轮廓系数选择的最佳K值为: {best_k} (轮廓系数 = {max(silhouette_scores):.4f})")
print(f"(Optimal K value based on silhouette score: {best_k} (Silhouette Score = {max(silhouette_scores):.4f}))")

# 5. 使用最佳K值训练K均值模型
print(f"\n使用最佳K值({best_k})训练K均值模型... (Training K-means model with optimal K value ({best_k})...)")
kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10)
kmeans.fit(features_scaled)
cluster_labels = kmeans.predict(features_scaled)
df['Cluster'] = cluster_labels

print("聚类完成! (Clustering completed!)")
print(f"各簇的样本数量: (Number of samples in each cluster:)")
for i in range(best_k):
    print(f"簇 {i}: {np.sum(cluster_labels == i)} 个样本 (Cluster {i}: {np.sum(cluster_labels == i)} samples)")

# 6. 可视化聚类结果 - 3D散点图
print("\n绘制3D散点图... (Plotting 3D scatter plot...)")
fig = plt.figure(figsize=(12, 10))
ax = fig.add_subplot(111, projection='3d')

# 为每个簇设置不同的颜色
colors = ['red', 'blue', 'green', 'purple', 'orange', 'brown', 'pink', 'gray', 'olive', 'cyan']
if best_k > len(colors):
    # 如果簇的数量超过预定义颜色数量，生成随机颜色
    colors = plt.cm.tab10(np.linspace(0, 1, best_k))

for i in range(best_k):
    cluster_data = df[df['Cluster'] == i]
    ax.scatter(
        cluster_data['Age'], 
        cluster_data['Annual Income (k$)'], 
        cluster_data['Spending Score (1-100)'],
        s=50, c=colors[i], label=f'簇 {i} (Cluster {i})', alpha=0.7
    )

ax.set_xlabel('年龄 (Age)')
ax.set_ylabel('年收入 (k$) (Annual Income (k$))')
ax.set_zlabel('消费得分 (1-100) (Spending Score (1-100))')
ax.set_title(f'顾客分群结果 (K={best_k})\n(Customer Segmentation Results (K={best_k}))')
ax.legend()

# 保存3D散点图
scatter3d_path = os.path.join(save_dir, "customer_clusters_3d.png")
plt.savefig(scatter3d_path, dpi=300, bbox_inches='tight')
plt.show()

print(f"3D散点图已保存为 '{scatter3d_path}' (3D scatter plot saved as '{scatter3d_path}')")

# 额外：2D散点图分析
print("\n绘制2D散点图分析... (Plotting 2D scatter plots for analysis...)")
fig, axes = plt.subplots(2, 2, figsize=(15, 12))

# 年龄 vs 年收入
scatter1 = axes[0, 0].scatter(df['Age'], df['Annual Income (k$)'], c=df['Cluster'], cmap='viridis', s=50, alpha=0.7)
axes[0, 0].set_xlabel('年龄 (Age)')
axes[0, 0].set_ylabel('年收入 (k$) (Annual Income (k$))')
axes[0, 0].set_title('年龄 vs 年收入\n(Age vs Annual Income)')
plt.colorbar(scatter1, ax=axes[0, 0])

# 年龄 vs 消费得分
scatter2 = axes[0, 1].scatter(df['Age'], df['Spending Score (1-100)'], c=df['Cluster'], cmap='viridis', s=50, alpha=0.7)
axes[0, 1].set_xlabel('年龄 (Age)')
axes[0, 1].set_ylabel('消费得分 (1-100) (Spending Score (1-100))')
axes[0, 1].set_title('年龄 vs 消费得分\n(Age vs Spending Score)')
plt.colorbar(scatter2, ax=axes[0, 1])

# 年收入 vs 消费得分
scatter3 = axes[1, 0].scatter(df['Annual Income (k$)'], df['Spending Score (1-100)'], c=df['Cluster'], cmap='viridis', s=50, alpha=0.7)
axes[1, 0].set_xlabel('年收入 (k$) (Annual Income (k$))')
axes[1, 0].set_ylabel('消费得分 (1-100) (Spending Score (1-100))')
axes[1, 0].set_title('年收入 vs 消费得分\n(Annual Income vs Spending Score)')
plt.colorbar(scatter3, ax=axes[1, 0])

# 隐藏最后一个子图
axes[1, 1].axis('off')

plt.tight_layout()

# 保存2D散点图
scatter2d_path = os.path.join(save_dir, "customer_clusters_2d.png")
plt.savefig(scatter2d_path, dpi=300, bbox_inches='tight')
plt.show()

print(f"2D散点图已保存为 '{scatter2d_path}' (2D scatter plots saved as '{scatter2d_path}')")

# 输出聚类中心
print("\n各簇的中心点 (原始尺度): (Cluster centers (original scale):)")
cluster_centers = scaler.inverse_transform(kmeans.cluster_centers_)
centers_df = pd.DataFrame(cluster_centers, columns=features.columns)
centers_df['Cluster'] = range(best_k)
print(centers_df)

print("\n聚类分析完成! (Clustering analysis completed!)")
print("=" * 60)
print("实验三顺利完成! (Experiment 3 completed successfully!)")
print("=" * 60)